package com.zang.blogz.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zang.blogz.constant.PublicConstant;
import com.zang.blogz.constant.RedisConstant;
import com.zang.blogz.constant.ResultConstant;
import com.zang.blogz.dto.ArticleHomeDTO;

import com.zang.blogz.enmus.ArticleTypeEnum;
import com.zang.blogz.enmus.FileExtEnum;
import com.zang.blogz.enmus.FilePathEnum;
import com.zang.blogz.entity.*;
import com.zang.blogz.exception.BizException;
import com.zang.blogz.mapper.ArticleMapper;
import com.zang.blogz.model.input.ro.ArticleReviseRO;
import com.zang.blogz.model.output.*;
import com.zang.blogz.model.output.admin.AdminArchiveVO;
import com.zang.blogz.result.Result;
import com.zang.blogz.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zang.blogz.strategy.SearchStrategy;
import com.zang.blogz.strategy.context.UploadStrategyContext;
import com.zang.blogz.utils.*;

import io.github.vampireachao.stream.core.collector.Collective;
import io.github.vampireachao.stream.core.optional.Opp;
import io.github.vampireachao.stream.core.optional.Sf;
import io.github.vampireachao.stream.core.stream.Steam;
import io.github.vampireachao.stream.plugin.mybatisplus.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 朵橙i
 * @since 2022-06-17
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {


    private static final String ARTICLE = "ZANG_BLOG";

    private static final String DETAILS = "details:";
    static HashMap<Integer, String> map;

    static {
        map = new HashMap<>();
        map.put(1, "原创");
        map.put(2, "转载");
        map.put(3, "翻译");
    }

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private WebsiteConfigService websiteConfigService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private ArticleTagService articleTagService;

    @Autowired
    private SearchStrategy searchStrategy;

    @Autowired
    private UploadStrategyContext uploadStrategyContext;

    /**
     * 获得首页文章列表
     *
     * @param pageNo   当前位置
     * @param pageSize 每页大小
     * @return Ipage对象
     */
    @Override
    public IPage<ArticleHomeDTO> getIndexArticleInfo(Integer pageNo, Integer pageSize) throws ExecutionException, InterruptedException {

        incTrafficByIp();

        Page<Article> page = new Page<>(pageNo, pageSize);
        CompletableFuture<IPage<Article>> async = CompletableFuture.supplyAsync(()
                -> Database.page(page, Wrappers.lambdaQuery(Article.class)
                .eq(Article::getIsTop, PublicConstant.ZERO)
                .orderByDesc(Article::getCreateTime)));

        CompletableFuture<Map<Integer, String>> categoryFuture = async.thenApply(pageOpp -> {
            List<Article> articles = pageOpp.getRecords();
            List<Integer> categoryIds = Steam.of(articles).map(Article::getCategoryId).collect(Collective.toList());
            return OneToOne.of(Category::getId).in(categoryIds).value(Category::getCategoryName).query();
        });

        CompletableFuture<Map<Integer, List<Tag>>> tagFuture = async.thenApply(pageOpp -> {
            List<Article> articles = pageOpp.getRecords();
            List<Integer> articleIds = Steam.of(articles).map(Article::getId).collect(Collective.toList());
            return OneToManyToOne.of(ArticleTag::getArticleId).in(articleIds).value(ArticleTag::getTagId).attachKey(Tag::getId).query();
        });

        final CompletableFuture<IPage<ArticleHomeDTO>> future = categoryFuture
                .thenCombine(tagFuture, (categoryMap, tagMap) -> {
                    try {
                        return async.thenApply(pageOpp -> pageOpp.convert(article -> {
                            ArticleHomeDTO dto = new ArticleHomeDTO();
                            dto.setId(article.getId());
                            dto.setArticleCover(article.getArticleCover());
                            dto.setArticleTitle(article.getArticleTitle());
                            dto.setArticleContent(article.getArticleContent());
                            dto.setCreateTime(article.getCreateTime());
                            dto.setIsTop(PublicConstant.ZERO);
                            dto.setType(ArticleTypeEnum.getMarkDownType(article.getType()));
                            dto.setCategoryName(categoryMap.get(article.getCategoryId()));
                            dto.setStatus(article.getStatus());
                            dto.setCategoryId(article.getCategoryId());
                            dto.setTagDTOList(tagMap.get(article.getId()));
                            return dto;
                        })).get();
                    } catch (InterruptedException | ExecutionException e) {
                        throw new RuntimeException(e);
                    }
                });

        return future.get();
    }

    /**
     * 获得归档文章列表
     *
     * @param pageNo   当前位置
     * @param pageSize 每页大小
     * @return Ipage对象
     */
    @Override
    public IPage<ArchiveVO> getListArchiveVo(Integer pageNo, Integer pageSize) {

        Page<Article> ipage = new Page<>(pageNo, pageSize);
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();

        queryWrapper.orderByDesc("createTime");

        IPage<Article> newPage = page(ipage, queryWrapper);

        return newPage.convert(result -> {
            ArchiveVO vo = new ArchiveVO();
            BeanUtil.copyProperties(result, vo);
            return vo;
        });

    }

    /**
     * 获得文章详情页信息
     *
     * @param id 文章id
     * @return 文章详情页对象
     */
    @Override
    public Result<ArticleDetailsVO> getBlogDetail(Integer id) {

        Article article = getById(id);
        if (Opp.of(article).isEmpty()) {

            return Result.error(ResultConstant.ARTICLE_NULL);
        }

        //推荐文章列表
        ArrayList<RecommendVO> recommendVoList = new ArrayList<>();

        //查询所有跟当前类型一样的文章
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();

        Sf.of(article.getCategoryId()).mayAlso(w -> queryWrapper.eq(Article::getCategoryId, article.getCategoryId()));
        queryWrapper.ne(Article::getId, id);

        CompletableFuture.runAsync(() -> list(queryWrapper)
                .stream().limit(6)
                .forEach(recommendArticleKey -> recommendVoList.add(
                        RecommendVO.builder()
                                .articleTitle(recommendArticleKey.getArticleTitle())
                                .articleCover(recommendArticleKey.getArticleCover())
                                .id(recommendArticleKey.getId())
                                .createTime(recommendArticleKey.getCreateTime())
                                .build())
                )
        );

        //最新文章
        ArrayList<LatestArticleVO> latestArticleVoList = new ArrayList<>();
        QueryWrapper<Article> latestQueryWrapper = new QueryWrapper<>();
        latestQueryWrapper.orderByDesc("createTime");

        list(latestQueryWrapper).stream()
                .limit(5).
                forEach(latestArticleKey -> latestArticleVoList
                        .add(LatestArticleVO.builder()
                                .id(latestArticleKey.getId())
                                .articleTitle(latestArticleKey.getArticleTitle())
                                .articleCover(latestArticleKey.getArticleCover())
                                .createTime(latestArticleKey.getCreateTime())
                                .build()
                        )
                );

        String title = article.getArticleTitle();
        String key = getKeyByIdAndHead(title);
        //文章访客
        Long traffic = redisService.bitCount(key);

        // 查询上一篇和下一篇文章

        Article lastArticle = Database.getOne(Wrappers.lambdaQuery(Article.class)
                        .select(Article::getId, Article::getArticleTitle, Article::getCreateTime, Article::getArticleCover)
                        .eq(Article::getIsDelete, PublicConstant.ZERO)
                        .eq(Article::getStatus, PublicConstant.ONE)
                        .lt(Article::getId, id)
                        .orderByDesc(Article::getId)
                        .last("LIMIT 1")
                , Boolean.FALSE);

        Article nextArticle = Database.getOne(Wrappers.lambdaQuery(Article.class)
                        .select(Article::getId, Article::getArticleTitle, Article::getCreateTime, Article:: getArticleCover)
                        .eq(Article::getIsDelete, PublicConstant.ZERO)
                        .eq(Article::getStatus, PublicConstant.ONE)
                        .gt(Article::getId, id)
                        .orderByAsc(Article::getId)
                        .last("LIMIT 1")
                , Boolean.FALSE);


        // 判断是否点赞
        String articleLikeKey = Opp.ofTry(() -> RedisConstant.ARTICLE_USER_LIKE + UserInfoUtil.getLoginUserId()).get();

        //保存基本信息
        ArticleDetailsVO articleDetailsVO = ArticleDetailsVO.builder().articleContent(article.getArticleContent())
                .articleTitle(article.getArticleTitle())
                .createTime(article.getCreateTime())
                .articleCover(article.getArticleCover())
                .traffic(traffic)
                .userName(One.of(UserInfo::getId).eq(article.getUserId()).value(UserInfo::getNickname).query())
                .isTop(article.getIsTop() > 0)
                .isLike(Opp.ofStr(articleLikeKey).isEmpty() ? Boolean.FALSE : redisService.sIsMember(articleLikeKey, id))
                .build();

        articleDetailsVO.setLastArchive(BeanUtil.toBean(lastArticle, ArchiveVO.class));
        articleDetailsVO.setNextArchive(BeanUtil.toBean(nextArticle, ArchiveVO.class));

        Object o = redisService.hGet(RedisConstant.ARTICLE_LIKE_COUNT, id.toString());
        Opp.of(o).ifPresent(data -> articleDetailsVO.setLikeCount(Integer.valueOf(data.toString()))).orElseRun(()->articleDetailsVO.setLikeCount(null));

        articleDetailsVO.setRecommendVoList(recommendVoList);
        articleDetailsVO.setLatestArticle(latestArticleVoList);

        return Result.ok(articleDetailsVO);
    }

    /**
     * 获取管理员文章列表页
     *
     * @return
     */
    @Override
    public Result<IPage<AdminArchiveVO>> getAdminArchiveVoList(Integer pageNo, Integer pageSize) {

        Page<Article> page = new Page<>(pageNo, pageSize);
        Page<Article> articlePage = page(page);

        IPage<AdminArchiveVO> convertPage = articlePage.convert(result -> {
            AdminArchiveVO vo = new AdminArchiveVO();
            BeanUtil.copyProperties(result, vo);
            return vo;
        });

        return Result.ok(convertPage);
    }

    @Override
    public Result<String> updateTopStatus(Integer articleId) {

        Article article = getById(articleId);
        if (article.getIsTop() == 0) {

            LambdaUpdateWrapper<Article> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

            lambdaUpdateWrapper.set(Article::getIsTop, 1);
            lambdaUpdateWrapper.eq(Article::getId, article.getId());

            update(lambdaUpdateWrapper);

            return Result.ok(ResultConstant.STATE_SUCCESS);
        }

        LambdaUpdateWrapper<Article> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

        lambdaUpdateWrapper.set(Article::getIsTop, 0);
        lambdaUpdateWrapper.eq(Article::getId, article.getId());

        update(lambdaUpdateWrapper);

        return Result.ok(ResultConstant.STATE_SUCCESS);
    }

    @Override
    @Transactional
    public Result<String> reviseOrSaveArticle(ArticleReviseRO articleReviseRo) {

        Article article = new Article();
        BeanUtil.copyProperties(articleReviseRo, article);
        article.setUserId(UserInfoUtil.getLoginUserId());

        // 保存文章分类
        Category category = saveArticleCategory(articleReviseRo);

        article.setCategoryId(category.getId());

        if (articleReviseRo.getId() != null) {

            //更新主表
            updateById(article);
            articleTagService.saveArticleTag(article.getId(), articleReviseRo.getTagList(), Boolean.FALSE);
            //更新中间表
            return Result.ok(ResultConstant.UPDATE_SUCCESS);
        } else {

            //新增主表
            save(article);
            //新增中间表
            articleTagService.saveArticleTag(article.getId(), articleReviseRo.getTagList(), Boolean.TRUE);
            return Result.ok(ResultConstant.RELEASE_SUCCESS);
        }
    }

    /**
     * 保存文章分类
     * @param articleReviseRo 文章
     * @return 分类
     */
    private Category saveArticleCategory(ArticleReviseRO articleReviseRo) {

        Category category = One.of(Category::getCategoryName).eq(articleReviseRo.getCategoryName()).query();

        if (Opp.of(category).isEmpty()){

            Category c = new Category();
            String orElse = Opp.ofStr(articleReviseRo.getCategoryName()).orElse("默认分类");
            c.setCategoryName(orElse);
            Database.save(c);
            return c;
        }
        return category;
    }


    @Override
    public Map<Integer, Integer> getArticleCount() {

        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();

        queryWrapper.select("count(id) AS id,category_id AS categoryId");
        queryWrapper.groupBy("category_id");

        List<Article> list = list(queryWrapper);

        return list.stream().collect(Collectors.toMap(Article::getCategoryId, Article::getId));

    }

    @Override
    public long getArticleCountByCategoryId(Integer categoryId) {

        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(Article::getCategoryId, categoryId);
        return count(queryWrapper);
    }

    @Override
    public List<ArticleTopVO> getTopArticle() {

        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(Article::getId, Article::getArticleCover, Article::getArticleTitle);
        List<Article> list = Many.of(Article::getIsTop).eq(PublicConstant.ONE).condition(q -> q.select(Article::getId, Article::getArticleCover, Article::getArticleTitle, Article::getStatus)).query();
        return BeanCopyUtils.copyList(list, ArticleTopVO.class);
    }

    @Override
    public Result<String> saveArticleLike(Integer articleId) {

        // 判断是否点赞
        String articleLikeKey = RedisConstant.ARTICLE_USER_LIKE + UserInfoUtil.getLoginUserId();
        if (redisService.sIsMember(articleLikeKey, articleId)) {
            // 点过赞则删除文章id
            redisService.sRemove(articleLikeKey, articleId);
            // 文章点赞量-1
            redisService.hDecr(RedisConstant.ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
            return Result.ok(ResultConstant.SUCCESS_THUMBS_DOWN);
        } else {
            // 未点赞则增加文章id
            redisService.sAdd(articleLikeKey, articleId);
            // 文章点赞量+1
            redisService.hIncr(RedisConstant.ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
            return Result.ok(ResultConstant.SUCCESS_THUMBS_UP);
        }
    }

    @Override
    public Result<List<ArticleSearchVO>> listArticlesBySearch(String keywords) {

       return Result.ok(searchStrategy.searchArticle(keywords));

    }

    @Override
    public List<String> exportArticles(List<Integer> articleIds) {

        ArrayList<String> urls = new ArrayList<>();
        // 查询文章信息
        List<Article> query = Many.of(Article::getId).in(articleIds).query();
        Steam.of(query).forEach(article -> {
            Opp<String> urlOpp = Opp.ofTry(() -> {

                ByteArrayInputStream inputStream = new ByteArrayInputStream(article.getArticleContent().getBytes());
                return uploadStrategyContext.executeUploadStrategy(article.getArticleTitle() + FileExtEnum.MD.getExtName(), inputStream, FilePathEnum.MD.getPath());
            });
            if (urlOpp.getException() != null) {

                throw new BizException(ResultConstant.FileMessage.FILE_EXPORT_E);
            }
            urls.add(urlOpp.get());
        });

        return urls;
    }

    /**
     * 异步的去增加访客量并且同步到数据库
     */
    @Async
    public void incTrafficByIp() {

        Long ip = getCanAddOff();

        redisService.hyperAdd(ARTICLE,ip.intValue());

        LambdaUpdateWrapper<WebsiteConfig> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(WebsiteConfig::getViewCount, redisService.hyperGet(ARTICLE));


        websiteConfigService.update(updateWrapper);
    }

    @Async
    public String getKeyByIdAndHead(String head) {

        redisService.hyperAdd(ARTICLE, getCanAddOff().intValue());
        return DETAILS + head;
    }


    public Long getCanAddOff() {

        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request =
                (HttpServletRequest)
                        Objects.requireNonNull(requestAttributes)
                                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 得到访问者的ip地址
        String ipAddress = IpUtil.getIpAddress(request);

        String s = StrUtil.removeAll(ipAddress, ".");

        int index = 0;
        long i = Long.parseLong(s);
        for (char c : s.toCharArray()) {
            if (c == '0') {
                index++;
            } else {
                break;
            }
        }
        if (index != 0) {

            i = Integer.parseInt(StrUtil.sub(s, 0, index));
        }
        return i;
    }
}
